A Commuter’s Challenge in Suburban Philadelphia

Every morning, Midge, a recent graduate working in downtown Philadelphia, starts her day battling the limitations of suburban bus transit. With services far less frequent than those in urban centers, her story illustrates the broader struggles faced by many suburban commuters, dependent on a system that doesn’t meet their needs.

Urban Efficiency vs. Suburban Scarcity

Using the latest SEPTA General Transit Feed Specification (GTFS) data1, it reveals that a staggering 80% of SEPTA’s trips are made by bus, showcasing the critical role of bus transit in Philadelphia’s transportation network. This statistic underlines the heavy reliance on buses across the city, not just in suburban areas.

# Import GTFS data
stops <- read.csv("data/google_bus/stops.txt", header = TRUE, sep = ",")
stop_times <- read.csv("data/google_bus/stop_times.txt", header = TRUE, sep = ",")
trips <- read.csv("data/google_bus/trips.txt", header = TRUE, sep = ",")
shapes <- read.csv("data/google_bus/shapes.txt", header = TRUE, sep = ",")
routes <- read.csv("data/google_bus/routes.txt", header = TRUE, sep = ",")
calendar <- read.csv("data/google_bus/calendar.txt", header = TRUE, sep = ",")
calendar_dates <- read.csv("data/google_bus/calendar_dates.txt", header = TRUE, sep = ",")

# Import Swiftly data
swiftly <- st_read("data/SwiftlyShapes/geoJSONs/SEPTASurface_HighResSpeed_v2.json")
# Join stop_times and trips, from now on dat will be our main data set
dat <- left_join(stop_times, trips, by = "trip_id")

# join dat and routes
dat <- left_join(dat, routes, by = "route_id")

#determine route_type
dat <- dat %>%
  mutate(mode = if_else(route_type == 0, "Trolley", ifelse(route_type == 1, "Metro", ifelse(route_type == 3, "Bus", "Trolleybus"))))

# Mode share (not ridership)
dat_trip_by_mode <- dat %>%
  group_by(mode) %>%
  summarise(mode_count = n(), .groups = 'drop') %>%
  mutate(total_count = sum(mode_count),
         mode_share = round(mode_count/sum(mode_count)*100, digits = 2)) %>%
  ungroup() 
  
# Plot mode share using ggplot2 and the custom palette
ggplot(dat_trip_by_mode, aes(x = mode, y = mode_share, fill = mode)) +
  geom_bar(stat = "identity", width = 0.75) +
  scale_fill_manual(values = bus_revolution_palette(length(unique(dat_trip_by_mode$mode)))) +
  geom_text(aes(label = paste0(mode_share, "% (", mode_count, " trips)")), 
            vjust = -0.5, color = "black", size = 2.5) +
  labs(title = "Mode Share by Transit Type",
       x = "Mode",
       y = "Mode Share (%)",
       fill = "Mode") +
  theme_minimal() +
  theme(legend.position = "right",
        plot.title = element_text(hjust = 0.5))

# total trip bus & trolley bus 1.980.505, total observations 2.026.677

The data also highlights distinct patterns in ridership, with bus usage peaking during morning (6-9AM) and evening (3-6PM) rush hours on weekdays. This contrasts with weekends, where the pattern flattens, indicating a more even distribution of trips throughout the day.

# CREATE LINE GRAPH OF TRIPS BY SERVICE PERIODS

# Function to normalize times greater than 24:00:00. Important step: check the stop_times$arrival_time and departure_time values if it's greater than 24:00 
normalize_time <- function(time_str) {
  time_parts <- strsplit(time_str, ":")[[1]]
  hours <- as.numeric(time_parts[1])
  minutes <- as.numeric(time_parts[2])
  seconds <- as.numeric(time_parts[3])
  
  if (hours >= 24) {
    hours <- hours - 24
    time_str <- sprintf("%02d:%02d:%02d", hours, minutes, seconds)
  }
  
  return(time_str)
}

#identify peak_pm category 
dat <- dat %>%
  mutate(departure_time = sapply(departure_time, normalize_time), # departure_time's normalized
         departure_time = lubridate::hms(departure_time),
         hour = hour(departure_time),  # Extract hour component for easier condition checking
         peak_category = if_else(hour >= 4 & hour < 6, "Early Morning",
                                    if_else(hour >= 6 & hour < 9, "AM Peak",
                                            if_else(hour >= 9 & hour < 15, "Mid Day",
                                                    if_else(hour >= 15 & hour < 18, "PM Peak",
                                                            if_else(hour >= 18 & hour < 22, "Evening", 
                                                                    ifelse(hour >= 22 & hour < 24, "Late Night", "Owl"))))))) 

# # Check if there are any NA values in the 'departure_time' column
# summarise(dat,
#     na_count = sum(is.na(departure_time)), #Count NA values
#     total_rows = n(),  # Total number of rows for context
#     percentage_na = na_count / total_rows * 100  # Percentage of NA values
#   )

durations <- c("Owl" = 4, "Early Morning" = 2, "AM Peak" = 3, "Mid Day" = 6, "PM Peak" = 3, "Evening" = 4, "Late Night" = 2)

dat_service_period <- dat %>%
  filter(mode %in% c("Bus", "Trolleybus", "Trolley")) %>%
  group_by(peak_category)%>%
  summarise(peak_count = n(), .groups = 'drop') %>%
  mutate(total_count = sum(peak_count),
         peak_share = round(peak_count/sum(peak_count)*100, digits = 2)) %>%
  arrange(factor(peak_category, levels = c("Owl", "Early Morning", "AM Peak", "Mid Day", "PM Peak", "Evening", "Late Night"))) %>%
  ungroup()

# Create a data frame for service periods
service_periods <- data.frame(
  peak_category = c("Owl", "Early Morning", "AM Peak", "Mid Day", "PM Peak", "Evening", "Late Night"),
  start_hour = c(0, 4, 6, 9, 15, 18, 22),
  end_hour = c(4, 6, 9, 15, 18, 22, 24)
)

# CREATE BAR CHART OF TRIPS BY SERVICE TYPES

# Ensure 'calendar' has 'day_type' defined
calendar <- calendar %>%
  mutate(
    day_type = case_when(
      monday == 1 & tuesday == 1 & wednesday == 1 & thursday == 1 & friday == 1 & saturday == 0 & sunday == 0 ~ "Weekday",
      (saturday == 1 | sunday == 1) ~ "Weekend",
      TRUE ~ "irregular"
    )
  )

# Calendar_dates includes 'service_id', 'date', and 'exception_type'
# Ensure 'calendar_dates' has 'day_type' defined based on the date
calendar_dates <- calendar_dates %>%
  filter(exception_type == 1) %>% #we only need exception_type == 1, those added to the service
  mutate(
    date = ymd(date),  # Convert date format if necessary
    day_of_week = wday(date, label = TRUE, week_start = 1),
    day_type = case_when(
      day_of_week %in% c("Mon", "Tue", "Wed", "Thu", "Fri") ~ "Weekday",  # Monday to Friday
      TRUE ~ "Weekend"  # Saturday and Sunday
    )
  )

# Join calendar_dates with calendar
calendar_joined <- calendar_dates %>%
  full_join(calendar, by = "service_id", suffix = c(".dates", ".cal")) %>%
  mutate(
    final_day_type = case_when(
      # Use calendar_dates' day_type if exception_type indicates added service
      exception_type == 1 & day_type.dates == "Weekday" ~ "Weekday",
      exception_type == 1 & day_type.dates == "Weekend" ~ "Weekend",
      # Default to calendar's day_type where there's no specific exception noted
      TRUE ~ day_type.cal
    )
  ) %>%
  select(service_id, final_day_type) %>%
  group_by(service_id, final_day_type) %>%
  summarise(count = n(), .groups = 'drop') %>%
  arrange(service_id, desc(count)) %>%
  distinct(service_id, .keep_all = TRUE) %>% # Make sure no double day types for service ID
  select(-count)
  
dat <- dat %>%
  left_join(calendar_joined, by = "service_id")

dat_service_type <- dat %>%
  filter(mode %in% c("Bus", "Trolleybus", "Trolley")) %>%
  group_by(final_day_type)%>%
  summarise(day_type_count = n(), .groups = 'drop') %>%
  mutate(total_count = sum(day_type_count),
         peak_share = round(day_type_count/sum(day_type_count)*100, digits = 2)) %>%
  arrange(factor(final_day_type, levels = c("Weekday", "Weekend"))) %>%
  ungroup()

# Summarize data to get the number of trips per hour
hourly_trips <- dat %>%
  filter(mode %in% c("Bus", "Trolleybus", "Trolley")) %>%
  group_by(hour, final_day_type) %>%
  summarise(number_of_trips = n())

# Define a custom palette for the service periods
service_period_colors <- c(
  "Owl" = "#E5E5E5",
  "Early Morning" = "#C5E0B4",
  "AM Peak" = "#A9D08E",
  "Mid Day" = "#FFD966",
  "PM Peak" = "#F4B084",
  "Evening" = "#D5A6BD",
  "Late Night" = "#A4C2F4"
)

# Plot the combined graph
ggplot() +
  # Add service period background rectangles
  geom_rect(data = service_periods, aes(xmin = start_hour, xmax = end_hour, ymin = 0, ymax = max(hourly_trips$number_of_trips), fill = peak_category), alpha = 0.3) +
  # Add the line plot for weekday trips
  geom_line(data = hourly_trips %>% filter(final_day_type == "Weekday"), aes(x = hour, y = number_of_trips), color = "#F2AE2E", size = 1) +
  geom_point(data = hourly_trips %>% filter(final_day_type == "Weekday"), aes(x = hour, y = number_of_trips), color = "#26A699", size = 2) +
  # Add the line plot for weekend trips
  geom_line(data = hourly_trips %>% filter(final_day_type == "Weekend"), aes(x = hour, y = number_of_trips), color = "#A6123A", size = 1) +
  geom_point(data = hourly_trips %>% filter(final_day_type == "Weekend"), aes(x = hour, y = number_of_trips), color = "#26A699", size = 2) +
  # Define scales and labels
  scale_x_continuous(breaks = 0:23) +
  scale_y_continuous(labels = comma) +
  scale_fill_manual(values = service_period_colors) +
  scale_color_manual(values = c("Weekday" = "#F2AE2E", 
                                "Weekend" = "#A6123A")) +
  labs(title = "Number of Trips by Hour with Service Periods",
       x = "Hour of Day",
       y = "Number of Trips",
       fill = "Service Period",
       color = "Day Type") +
  theme_minimal() +
  theme(legend.position = "bottom",
        plot.title = element_text(hjust = 0.5))

While bus services are integral to Philadelphia’s transit system, not all areas benefit equally. Data specifically pertaining to suburban regions during weekday AM and PM peak hours reveals a stark disparity: most suburban bus stations face waiting times ranging from 30 to 60 minutes between buses. This significant wait time not only underscores the challenges faced by commuters like Midge but also highlights a broader issue affecting many suburban residents across Southeastern Pennsylvania, who rely heavily on bus transit.

# SET UP STOPS CATEGORY. This step is crucial as we'll use stops information and join them for each kind of maps 

# Convert stops data to sf object
stops <- stops %>%
  st_as_sf(coords = c("stop_lon", "stop_lat"), crs = 4326, remove = FALSE)

# Load PA and SEPTA region map for base map
PA <- st_read("data/PaCounty2024_05.geojson")
SEPTA_region_map <- PA[PA$COUNTY_NAM %in% c("MONTGOMERY", "BUCKS", "PHILADELPHIA", "DELAWARE", "CHESTER"), ] %>%
  select(PA_CTY_COD, COUNTY_NAM, FIPS_COUNT)

# Load Center City boundary
center_city <- st_read("data/20240708_CenterCity/CenterCity.shp")

# Ensure all datasets are in the same CRS
SEPTA_region_map <- st_transform(SEPTA_region_map, crs = st_crs(stops))
center_city <- st_transform(center_city, crs = st_crs(stops))

# Perform the spatial intersection for county
stops <- stops %>%
  st_join(SEPTA_region_map, left = TRUE, join = st_intersects) %>%
  rename(county = COUNTY_NAM) %>%
  mutate(center_city = st_within(stops, center_city, sparse = FALSE))%>%
  mutate(center_city = rowSums(center_city) > 0)  # Convert logical matrix to vector

# Categorize the stops
stops <- stops %>%
  mutate(category = case_when(
    county == "PHILADELPHIA" & center_city == TRUE ~ "PHILADELPHIA-Center_City",
    county == "PHILADELPHIA" & center_city == FALSE ~ "PHILADELPHIA-Not_Center_City",
    TRUE ~ county
  )) %>%
  select(-"center_city")
# SET UP HOURLY AVERAGE TRIP DATA FOR EACH BUS STOP

dat_trip_count_hr <- dat %>%
  filter(mode %in% c("Bus", "Trolleybus", "Trolley")) %>%
  group_by(stop_id, mode, final_day_type) %>%
  summarise(trip_count = n(), 
            All_Routes = paste(unique(route_id), collapse = ", "),
            .groups = 'drop') %>%
  mutate(avg_trip_hr = round(trip_count/24)) %>%
  mutate(log_avg_trip_hr = round(log1p(avg_trip_hr), digits = 0)) %>%
  mutate(max_freq = ifelse(avg_trip_hr >= 12, "5 MAX",
                           ifelse (avg_trip_hr >= 6, "10 MAX",
                           ifelse (avg_trip_hr >= 4, "15 MAX", 
                           ifelse(avg_trip_hr >= 2, "30 MAX", "60 MAX")))))

dat_trip_count_hr <- left_join(dat_trip_count_hr, stops, by = "stop_id")

dat_trip_count_hr <- st_as_sf(dat_trip_count_hr, coords = c("stop_lon", "stop_lat"), crs = 4326, remove = FALSE)

# sum(dat_trip_count_hr$trip_count) #1980505 <- Always make sure you retain the number of observation
# SET UP AVERAGE TRIP DATA PER SERVICE PERIOD FOR EACH BUS STOP

dat_trip_count_prd <- dat %>%
  filter(mode %in% c("Bus", "Trolleybus", "Trolley")) %>%
  group_by(stop_id, peak_category, mode, final_day_type) %>%
  summarise(trip_count = n(), 
            All_Routes = paste(unique(route_id), collapse = ", "),
            .groups = 'drop') %>%
  mutate(
    hours = durations[peak_category],
    avg_trip_hr = round(trip_count / hours)
  ) %>%
  select(-hours) %>%
  mutate(log_avg_trip_hr = round(log1p(avg_trip_hr), digits = 0)) %>%
  mutate(max_freq = ifelse(avg_trip_hr >= 12, "5 MAX",
                           ifelse (avg_trip_hr >= 6, "10 MAX",
                           ifelse (avg_trip_hr >= 4, "15 MAX", 
                           ifelse(avg_trip_hr >= 2, "30 MAX", "60 MAX")))))

dat_trip_count_prd <- left_join(dat_trip_count_prd, stops, by = "stop_id")

dat_trip_count_prd <- st_as_sf(dat_trip_count_prd, coords = c("stop_lon", "stop_lat"), crs = 4326, remove = FALSE)

# sum(dat_trip_count_prd$trip_count)  #1980505 <- Always make sure you retain the number of observation
SEPTA_region_map <- st_transform(SEPTA_region_map, st_crs(dat_trip_count_hr))

# # Define color palette
# pal <- colorNumeric(palette = viridisLite::viridis(256, option = "C"), domain = dat_trip_count_hr$avg_trip_hr)

# Define the color codes for bus revolution and reverse them
bus_revolution_colors <- rev(c("#A6123A", "#655BA6", "#26A699", "#F2AE2E", "#F2E5D5"))

# Ensure max_freq is a factor with levels matching the colors
dat_trip_count_hr$max_freq <- factor(dat_trip_count_hr$max_freq, levels = c("5 MAX", "10 MAX", "15 MAX", "30 MAX", "60 MAX"))

# Define the color palette for max_freq
max_freq_colors <- c("5 MAX" = "#4B0082", "10 MAX" = "#FF4500", "15 MAX" = "#A6123A", "30 MAX" = "#26A699", "60 MAX" = "#F2AE2E")
pal_max_freq <- colorFactor(palette = max_freq_colors, domain = dat_trip_count_hr$max_freq)

# # Generate the list for overlayGroups with the desired order
# overlay_groups <- expand.grid(final_day_type = c("Weekday", "Weekend"), peak_category = peak_order) %>%
#   mutate(group_name = paste(final_day_type, peak_category, sep = "_")) %>%
#   pull(group_name)

# Create the leaflet map
m_avg_trip_hr <- leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addPolygons(data = SEPTA_region_map, color = "black", weight = 1, fill = FALSE) %>%
  addCircleMarkers(data = dat_trip_count_hr %>% filter(final_day_type == "Weekday"),
                   group = "Weekday",
                   lng = ~stop_lon, lat = ~stop_lat,
                   color = ~pal_max_freq(max_freq),
                   radius = 1,
                   opacity = 0.8,
                   popup = ~paste0("<b>Stop ID:</b> ", stop_id, "<br>",
                                   "<b>Stop Name:</b> ", stop_name, "<br>",
                                   "<b>Avg Buses/Hr:</b> ", avg_trip_hr, "<br>",
                                   "<b>Max Frequency:</b> ", max_freq, "<br>",
                                   "<b>All Routes:</b> ", All_Routes, "<br>",
                                   "<b>Day Type:</b> ", final_day_type)) %>%
  addCircleMarkers(data = dat_trip_count_hr %>% filter(final_day_type == "Weekend"),
                   group = "Weekend",
                   lng = ~stop_lon, lat = ~stop_lat,
                   color = ~pal_max_freq(max_freq),
                   radius = 1,
                   opacity = 0.8,
                   popup = ~paste0("<b>Stop ID:</b> ", stop_id, "<br>",
                                   "<b>Stop Name:</b> ", stop_name, "<br>",
                                   "<b>Avg Buses/Hr:</b> ", avg_trip_hr, "<br>",
                                   "<b>Max Frequency:</b> ", max_freq, "<br>",
                                   "<b>All Routes:</b> ", All_Routes, "<br>",
                                   "<b>Day Type:</b> ", final_day_type)) %>%
  addLayersControl(
    overlayGroups = c("Weekday", "Weekend"),
    options = layersControlOptions(collapsed = FALSE)
  ) %>%
  hideGroup(c("Weekend")) %>%
  addLegend(pal = pal_max_freq, values = dat_trip_count_hr$max_freq, title = "Max Frequency",
            position = "bottomright")

m_avg_trip_hr

Further analysis of bus stop data by county—excluding Philadelphia’s Center City—reveals a misleading equality in service distribution across the counties. While the top bus stops in each county boast a 5-minute maximum headway, reflecting seemingly equal service levels, the actual number of buses tells a different story. For instance, even at their busiest, bus stops in Bucks County see an average of only 15 buses per hour at the 5 MAX category, starkly less than the 30 buses seen in similar categories in Philadelphia’s outlying areas. This discrepancy highlights the concentration of better services still within closer proximity to urban centers, despite attempts at equitable distribution.

# Filter and select the top 10 stops for 'mode' == 'bus' on the weekday
top_stops_hr_weekday <- dat_trip_count_hr %>%
  filter(mode %in% c("Bus","Trolleybus"), final_day_type == "Weekday") %>%
  select(stop_id, stop_name, avg_trip_hr, All_Routes) %>%
  arrange(desc(avg_trip_hr)) %>%
  slice_head(n = 10)

# Create a table with kable and enhance it with kableExtra
kable(top_stops_hr_weekday, "html", booktabs = TRUE, 
      caption = "Top 10 Bus Stops by Average Trips Per Hour on the Weekday",
      align = c('l', rep('l', ncol(top_stops_hr_weekday) - 1))) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE) %>%
  column_spec(1, bold = TRUE, color = "#655BA6", width = "5em") %>%
  column_spec(3, bold = TRUE, color = "#A6123A", extra_css = "text-align: right;")
Top 10 Bus Stops by Average Trips Per Hour on the Weekday
stop_id stop_name avg_trip_hr All_Routes geometry
21204 Frankford Transportation Center - Main Dropoff 47 14, 19, 20, 24, 26, 3, 50, 58, 67, 88, BLVDDIR, MFO POINT (-75.07748 40.02329)
1148 69th St Transportation Center South Terminal 44 103, 104, 105, 107, 108, 109, 111, 113, 110, 120, 21, 30, 65, 68, MFO POINT (-75.25828 39.96208)
10266 Market St & 15th St 40 124, 125, 17, 31, 32, 33, 38, 44, 48, 62, BSO, MFO POINT (-75.16548 39.95255)
10272 Market St & 18th St 36 124, 125, 17, 31, 32, 33, 38, 44, 48, 62 POINT (-75.17017 39.95312)
18451 Market St & 16th St 36 124, 125, 17, 31, 32, 33, 38, 44, 48, 62 POINT (-75.16705 39.95274)
136 Cheltenham Av & Ogontz Av Loop 34 16, 6, H, XH POINT (-75.1591 40.07483)
17842 JFK Blvd & 15th St 34 MANNLP, 124, 125, 17, 27, 31, 32, 33, 38, 44, 446, 62, 78 POINT (-75.16476 39.95365)
8936 JFK Blvd & 17th St 31 MANNLP, 124, 125, 17, 31, 32, 33, 38, 44, 446, 62 POINT (-75.16806 39.95406)
101 Pier 70 WalMart & Home Depot 29 25, 29, 64, 7 POINT (-75.14167 39.92531)
15497 69th St Transit Center 29 104, 109, 110, 111, 112, 120, 123, 126 POINT (-75.25968 39.96217)
# Define a custom palette for the categories
category_colors <- c(
  "BUCKS" = "#A9D08E",
  "CHESTER" = "#FFD966",
  "DELAWARE" = "#F4B084",
  "MONTGOMERY" = "#D5A6BD",
  "PHILADELPHIA-Not_Center_City" = "#A4C2F4"
)

# Example with Weekday data
dat_filtered_weekday <- dat_trip_count_hr %>%
  filter(mode %in% c("Bus","Trolleybus"), final_day_type == "Weekday" & category != "PHILADELPHIA-Center_City")

# Function to get top 5 stops per category
get_top_stops_per_category <- function(data, n = 5) {
  data %>%
    group_by(category) %>%
    select(stop_id, stop_name, avg_trip_hr, max_freq, All_Routes, category) %>%
    arrange(desc(avg_trip_hr)) %>%
    slice_head(n = n) %>%
    ungroup()
}

# Get top 5 stops per category for Weekday
top_stops_hr_weekday <- get_top_stops_per_category(dat_filtered_weekday, n = 5)

# Create the table for Weekday with row colors
kable(top_stops_hr_weekday, "html", booktabs = TRUE, 
      caption = "Top 5 Bus Stops by Average Trips Per Hour on the Weekday",
      align = c('l', rep('l', ncol(top_stops_hr_weekday) - 1))) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE) %>%
  column_spec(1, bold = TRUE, color = "#655BA6", width = "5em") %>%
  column_spec(3, bold = TRUE, color = "#A6123A", extra_css = "text-align: right;") 
Top 5 Bus Stops by Average Trips Per Hour on the Weekday
stop_id stop_name avg_trip_hr max_freq All_Routes category geometry
330 Neshaminy Mall 16 5 MAX 128, 130, 14, 58, BLVDDIR BUCKS POINT (-74.95357 40.13806)
23080 Rockhill Rd & Neshaminy Rd - MBFS 14 5 MAX 128, 130, 14, 58, BLVDDIR BUCKS POINT (-74.95696 40.13665)
23071 Rockhill Rd & Neshaminy Blvd - MBNS 13 5 MAX 14, 58, BLVDDIR BUCKS POINT (-74.95725 40.13658)
22762 Roosevelt Blvd & Old Lincoln Hwy - MBFS 12 5 MAX 1, 14, BLVDDIR BUCKS POINT (-74.97913 40.12061)
25881 Roosevelt Blvd & Old Lincoln Hwy - MBNS 12 5 MAX 1, 14, BLVDDIR BUCKS POINT (-74.97909 40.12022)
1990 West Chester Transportation Center 8 10 MAX 135, 92, 104 CHESTER POINT (-75.6073 39.9579)
18729 Chestnut St & Church St 6 10 MAX 135, 92, 104 CHESTER POINT (-75.60707 39.96102)
18730 Chestnut St & Darlington St 6 10 MAX 135, 92, 104 CHESTER POINT (-75.60838 39.96047)
30146 New St & Chestnut St - FS 6 10 MAX 135, 92, 104 CHESTER POINT (-75.60984 39.95955)
14820 Church St & University Av - FS 5 15 MAX 104 CHESTER POINT (-75.6 39.95252)
1148 69th St Transportation Center South Terminal 44 5 MAX 103, 104, 105, 107, 108, 109, 111, 113, 110, 120, 21, 30, 65, 68, MFO DELAWARE POINT (-75.25828 39.96208)
15497 69th St Transit Center 29 5 MAX 104, 109, 110, 111, 112, 120, 123, 126 DELAWARE POINT (-75.25968 39.96217)
2024 69th St Transportation Center North Terminal 25 5 MAX 103, 105, 106, 123, 30, 65 DELAWARE POINT (-75.25861 39.96282)
14785 Chester Transportation Center 20 5 MAX 117, 119, 113, 37 DELAWARE POINT (-75.35997 39.84921)
30597 Darby Transportation Center - bus 17 5 MAX 114, 115, 113 DELAWARE POINT (-75.26358 39.9191)
136 Cheltenham Av & Ogontz Av Loop 34 5 MAX 16, 6, H, XH MONTGOMERY POINT (-75.1591 40.07483)
1649 Norristown Transportation Center 22 5 MAX 131, 90, 93, 96, 97, 98, 99 MONTGOMERY POINT (-75.34482 40.11345)
809 Willow Grove Park Mall 20 5 MAX 310, 311, 95, 22, 55 MONTGOMERY POINT (-75.12249 40.14226)
1827 Plaza at King of Prussia 19 5 MAX 124, 139, 92, 99, 123, 125 MONTGOMERY POINT (-75.39311 40.08983)
246 Plymouth Meeting Mall 16 5 MAX 150, 90, 95, 98, 27, L MONTGOMERY POINT (-75.28403 40.1172)
21204 Frankford Transportation Center - Main Dropoff 47 5 MAX 14, 19, 20, 24, 26, 3, 50, 58, 67, 88, BLVDDIR, MFO PHILADELPHIA-Not_Center_City POINT (-75.07748 40.02329)
101 Pier 70 WalMart & Home Depot 29 5 MAX 25, 29, 64, 7 PHILADELPHIA-Not_Center_City POINT (-75.14167 39.92531)
28 Wissahickon Transportation Center - onsite 28 5 MAX 124, 125, 1, 35, 38, 61, R PHILADELPHIA-Not_Center_City POINT (-75.20724 40.0149)
8712 Hunting Park Av & 22nd St 27 5 MAX 33, 445, 447, 56, H, R, XH PHILADELPHIA-Not_Center_City POINT (-75.16457 40.01089)
410 Philadelphia Mills & Marshalls 24 5 MAX 129, 130, 20, 50, 67, 84 PHILADELPHIA-Not_Center_City POINT (-74.96447 40.08583)

Impact and Innovation: Enhancing Suburban Bus Services

While the raw data illuminates the service disparities, the true depth of this issue is best captured through the lived experiences of suburban commuters like Midge. Personal stories reveal the broader human impact: missed opportunities, prolonged workdays, and diminished family life due to infrequent bus services. This qualitative insight paints a vivid picture of the daily challenges and underscores the urgency for targeted improvements.

SEPTA’s ongoing “Bus Revolution” project aims to modernize and enhance bus services, promising updated routes and improved frequencies that haven’t been revised since the 1960s. While the initiative seeks to reduce headways to a maximum of 30 minutes even in the most remote suburban areas, it has faced delays and criticism, particularly concerning its potential to disproportionately impact low-income neighborhoods. This tension highlights the complex interplay between improving service efficiency and addressing community concerns of gentrification.

As we conclude our exploration of suburban bus transit disparities in Philadelphia, the compelling narratives of daily commuters, underpinned by robust SEPTA data, paint a clear picture of the challenges and the urgent need for systemic improvements. The “Bus Revolution” project, while a promising step toward enhancing bus frequencies and updating decades-old routes, also highlights the delicate balance required to improve service efficiency without exacerbating social inequities.

Embracing innovative solutions and fostering community engagement in transit planning are crucial. By committing to both technological upgrades and equitable service distribution, SEPTA can ensure that every commuter, regardless of where they live, has reliable and timely access to public transit. This not only improves individual daily experiences but also supports broader goals of economic mobility and sustainable urban development.

As stakeholders, we are called upon to support these initiatives, advocate for transparent and inclusive planning processes, and ensure that the voices of all commuters, especially those most affected like Midge, are heard and addressed in the pursuit of a truly connected and equitable transit system.

LS0tDQp0aXRsZTogIldhaXRpbmcgZm9yIENoYW5nZTogVGhlIFVyZ2VudCBOZWVkIGZvciBFcXVpdGFibGUgQnVzIFNlcnZpY2VzIGluIFN1YnVyYmFuIFBoaWxhZGVscGhpYSINCmF1dGhvcjogIlJlZ3kgU2VwdGlhbiINCmRhdGU6ICIyMDI0LTA5LTI0Ig0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6ICJoaWRlIg0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgY2FjaGUgPSBGQUxTRSkNCg0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeSh0aWR5dHJhbnNpdCkNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShobXMpDQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShwYXRjaHdvcmspDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KHdrKQ0KbGlicmFyeShsZWFmbGV0KQ0KbGlicmFyeShsZWFmbGV0LmV4dHJhcykNCmxpYnJhcnkodmlyaWRpcykNCmxpYnJhcnkodmlyaWRpc0xpdGUpDQpsaWJyYXJ5KGh0bWx3aWRnZXRzKQ0KbGlicmFyeShodG1sdG9vbHMpDQpsaWJyYXJ5KGxlYWZsZXQuZXh0cmFzKQ0KbGlicmFyeShmb3JjYXRzKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShzY2FsZXMpDQpsaWJyYXJ5KHNoaW55KQ0KbGlicmFyeSh3ZWJzaG90MikNCmxpYnJhcnkobWFnaWNrKQ0KDQojIERlZmluZSB0aGUgY29sb3IgY29kZXMNCmJ1c19yZXZvbHV0aW9uX2NvbG9ycyA8LSBjKCIjQTYxMjNBIiwgIiM2NTVCQTYiLCAiIzI2QTY5OSIsICIjRjJBRTJFIiwgIiNGMkU1RDUiKQ0KDQojIENyZWF0ZSBhIGN1c3RvbSBwYWxldHRlIGZ1bmN0aW9uDQpidXNfcmV2b2x1dGlvbl9wYWxldHRlIDwtIGZ1bmN0aW9uKG4pIHsNCiAgY29sb3JSYW1wUGFsZXR0ZShidXNfcmV2b2x1dGlvbl9jb2xvcnMpKG4pDQp9DQoNCndvcmtkaXIgPC0gIn4vR2l0SHViL01VU0EtNjMxMF9Bc3NpZ25tZW50LTIiICMgQ2hhbmdlIHRoZSBkaXJlY3RvcnkNCnNldHdkKHdvcmtkaXIpDQpgYGANCiMjIyBBIENvbW11dGVyJ3MgQ2hhbGxlbmdlIGluIFN1YnVyYmFuIFBoaWxhZGVscGhpYSAgDQoNCkV2ZXJ5IG1vcm5pbmcsIE1pZGdlLCBhIHJlY2VudCBncmFkdWF0ZSB3b3JraW5nIGluIGRvd250b3duIFBoaWxhZGVscGhpYSwgc3RhcnRzIGhlciBkYXkgYmF0dGxpbmcgdGhlIGxpbWl0YXRpb25zIG9mIHN1YnVyYmFuIGJ1cyB0cmFuc2l0LiBXaXRoIHNlcnZpY2VzIGZhciBsZXNzIGZyZXF1ZW50IHRoYW4gdGhvc2UgaW4gdXJiYW4gY2VudGVycywgaGVyIHN0b3J5IGlsbHVzdHJhdGVzIHRoZSBicm9hZGVyIHN0cnVnZ2xlcyBmYWNlZCBieSBtYW55IHN1YnVyYmFuIGNvbW11dGVycywgZGVwZW5kZW50IG9uIGEgc3lzdGVtIHRoYXQgZG9lc24ndCBtZWV0IHRoZWlyIG5lZWRzLiAgDQoNCiMjIyBVcmJhbiBFZmZpY2llbmN5IHZzLiBTdWJ1cmJhbiBTY2FyY2l0eSAgDQoNClVzaW5nIHRoZSBsYXRlc3QgU0VQVEEgR2VuZXJhbCBUcmFuc2l0IEZlZWQgU3BlY2lmaWNhdGlvbiAoR1RGUykgZGF0YVteMV0sIGl0IHJldmVhbHMgdGhhdCBhIHN0YWdnZXJpbmcgODAlIG9mIFNFUFRBJ3MgdHJpcHMgYXJlIG1hZGUgYnkgYnVzLCBzaG93Y2FzaW5nIHRoZSBjcml0aWNhbCByb2xlIG9mIGJ1cyB0cmFuc2l0IGluIFBoaWxhZGVscGhpYeKAmXMgdHJhbnNwb3J0YXRpb24gbmV0d29yay4gVGhpcyBzdGF0aXN0aWMgdW5kZXJsaW5lcyB0aGUgaGVhdnkgcmVsaWFuY2Ugb24gYnVzZXMgYWNyb3NzIHRoZSBjaXR5LCBub3QganVzdCBpbiBzdWJ1cmJhbiBhcmVhcy4gDQoNClteMV06IFtTRVBUQSBHVEZTXShodHRwczovL3d3dzMuc2VwdGEub3JnL2RldmVsb3Blci9ndGZzX3B1YmxpYy56aXApDQoNCg0KYGBge3IgaW1wb3J0IGd0ZnMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUUsIHJlc3VsdHMgPSAnaGlkZSd9DQojIEltcG9ydCBHVEZTIGRhdGENCnN0b3BzIDwtIHJlYWQuY3N2KCJkYXRhL2dvb2dsZV9idXMvc3RvcHMudHh0IiwgaGVhZGVyID0gVFJVRSwgc2VwID0gIiwiKQ0Kc3RvcF90aW1lcyA8LSByZWFkLmNzdigiZGF0YS9nb29nbGVfYnVzL3N0b3BfdGltZXMudHh0IiwgaGVhZGVyID0gVFJVRSwgc2VwID0gIiwiKQ0KdHJpcHMgPC0gcmVhZC5jc3YoImRhdGEvZ29vZ2xlX2J1cy90cmlwcy50eHQiLCBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiLCIpDQpzaGFwZXMgPC0gcmVhZC5jc3YoImRhdGEvZ29vZ2xlX2J1cy9zaGFwZXMudHh0IiwgaGVhZGVyID0gVFJVRSwgc2VwID0gIiwiKQ0Kcm91dGVzIDwtIHJlYWQuY3N2KCJkYXRhL2dvb2dsZV9idXMvcm91dGVzLnR4dCIsIGhlYWRlciA9IFRSVUUsIHNlcCA9ICIsIikNCmNhbGVuZGFyIDwtIHJlYWQuY3N2KCJkYXRhL2dvb2dsZV9idXMvY2FsZW5kYXIudHh0IiwgaGVhZGVyID0gVFJVRSwgc2VwID0gIiwiKQ0KY2FsZW5kYXJfZGF0ZXMgPC0gcmVhZC5jc3YoImRhdGEvZ29vZ2xlX2J1cy9jYWxlbmRhcl9kYXRlcy50eHQiLCBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiLCIpDQoNCiMgSW1wb3J0IFN3aWZ0bHkgZGF0YQ0Kc3dpZnRseSA8LSBzdF9yZWFkKCJkYXRhL1N3aWZ0bHlTaGFwZXMvZ2VvSlNPTnMvU0VQVEFTdXJmYWNlX0hpZ2hSZXNTcGVlZF92Mi5qc29uIikNCg0KYGBgDQoNCmBgYHtyIHRvdGFsIHRyaXAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUV9DQojIEpvaW4gc3RvcF90aW1lcyBhbmQgdHJpcHMsIGZyb20gbm93IG9uIGRhdCB3aWxsIGJlIG91ciBtYWluIGRhdGEgc2V0DQpkYXQgPC0gbGVmdF9qb2luKHN0b3BfdGltZXMsIHRyaXBzLCBieSA9ICJ0cmlwX2lkIikNCg0KIyBqb2luIGRhdCBhbmQgcm91dGVzDQpkYXQgPC0gbGVmdF9qb2luKGRhdCwgcm91dGVzLCBieSA9ICJyb3V0ZV9pZCIpDQoNCiNkZXRlcm1pbmUgcm91dGVfdHlwZQ0KZGF0IDwtIGRhdCAlPiUNCiAgbXV0YXRlKG1vZGUgPSBpZl9lbHNlKHJvdXRlX3R5cGUgPT0gMCwgIlRyb2xsZXkiLCBpZmVsc2Uocm91dGVfdHlwZSA9PSAxLCAiTWV0cm8iLCBpZmVsc2Uocm91dGVfdHlwZSA9PSAzLCAiQnVzIiwgIlRyb2xsZXlidXMiKSkpKQ0KDQojIE1vZGUgc2hhcmUgKG5vdCByaWRlcnNoaXApDQpkYXRfdHJpcF9ieV9tb2RlIDwtIGRhdCAlPiUNCiAgZ3JvdXBfYnkobW9kZSkgJT4lDQogIHN1bW1hcmlzZShtb2RlX2NvdW50ID0gbigpLCAuZ3JvdXBzID0gJ2Ryb3AnKSAlPiUNCiAgbXV0YXRlKHRvdGFsX2NvdW50ID0gc3VtKG1vZGVfY291bnQpLA0KICAgICAgICAgbW9kZV9zaGFyZSA9IHJvdW5kKG1vZGVfY291bnQvc3VtKG1vZGVfY291bnQpKjEwMCwgZGlnaXRzID0gMikpICU+JQ0KICB1bmdyb3VwKCkgDQogIA0KIyBQbG90IG1vZGUgc2hhcmUgdXNpbmcgZ2dwbG90MiBhbmQgdGhlIGN1c3RvbSBwYWxldHRlDQpnZ3Bsb3QoZGF0X3RyaXBfYnlfbW9kZSwgYWVzKHggPSBtb2RlLCB5ID0gbW9kZV9zaGFyZSwgZmlsbCA9IG1vZGUpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNzUpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYnVzX3Jldm9sdXRpb25fcGFsZXR0ZShsZW5ndGgodW5pcXVlKGRhdF90cmlwX2J5X21vZGUkbW9kZSkpKSkgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcGFzdGUwKG1vZGVfc2hhcmUsICIlICgiLCBtb2RlX2NvdW50LCAiIHRyaXBzKSIpKSwgDQogICAgICAgICAgICB2anVzdCA9IC0wLjUsIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDIuNSkgKw0KICBsYWJzKHRpdGxlID0gIk1vZGUgU2hhcmUgYnkgVHJhbnNpdCBUeXBlIiwNCiAgICAgICB4ID0gIk1vZGUiLA0KICAgICAgIHkgPSAiTW9kZSBTaGFyZSAoJSkiLA0KICAgICAgIGZpbGwgPSAiTW9kZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IiwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpDQoNCiMgdG90YWwgdHJpcCBidXMgJiB0cm9sbGV5IGJ1cyAxLjk4MC41MDUsIHRvdGFsIG9ic2VydmF0aW9ucyAyLjAyNi42NzcNCmBgYA0KDQpUaGUgZGF0YSBhbHNvIGhpZ2hsaWdodHMgZGlzdGluY3QgcGF0dGVybnMgaW4gcmlkZXJzaGlwLCB3aXRoIGJ1cyB1c2FnZSBwZWFraW5nIGR1cmluZyBtb3JuaW5nICg2LTlBTSkgYW5kIGV2ZW5pbmcgKDMtNlBNKSBydXNoIGhvdXJzIG9uIHdlZWtkYXlzLiBUaGlzIGNvbnRyYXN0cyB3aXRoIHdlZWtlbmRzLCB3aGVyZSB0aGUgcGF0dGVybiBmbGF0dGVucywgaW5kaWNhdGluZyBhIG1vcmUgZXZlbiBkaXN0cmlidXRpb24gb2YgdHJpcHMgdGhyb3VnaG91dCB0aGUgZGF5Lg0KICANCmBgYHtyIHBlYWsgdGltZSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSwgcmVzdWx0cyA9ICdoaWRlJ30NCiMgQ1JFQVRFIExJTkUgR1JBUEggT0YgVFJJUFMgQlkgU0VSVklDRSBQRVJJT0RTDQoNCiMgRnVuY3Rpb24gdG8gbm9ybWFsaXplIHRpbWVzIGdyZWF0ZXIgdGhhbiAyNDowMDowMC4gSW1wb3J0YW50IHN0ZXA6IGNoZWNrIHRoZSBzdG9wX3RpbWVzJGFycml2YWxfdGltZSBhbmQgZGVwYXJ0dXJlX3RpbWUgdmFsdWVzIGlmIGl0J3MgZ3JlYXRlciB0aGFuIDI0OjAwIA0Kbm9ybWFsaXplX3RpbWUgPC0gZnVuY3Rpb24odGltZV9zdHIpIHsNCiAgdGltZV9wYXJ0cyA8LSBzdHJzcGxpdCh0aW1lX3N0ciwgIjoiKVtbMV1dDQogIGhvdXJzIDwtIGFzLm51bWVyaWModGltZV9wYXJ0c1sxXSkNCiAgbWludXRlcyA8LSBhcy5udW1lcmljKHRpbWVfcGFydHNbMl0pDQogIHNlY29uZHMgPC0gYXMubnVtZXJpYyh0aW1lX3BhcnRzWzNdKQ0KICANCiAgaWYgKGhvdXJzID49IDI0KSB7DQogICAgaG91cnMgPC0gaG91cnMgLSAyNA0KICAgIHRpbWVfc3RyIDwtIHNwcmludGYoIiUwMmQ6JTAyZDolMDJkIiwgaG91cnMsIG1pbnV0ZXMsIHNlY29uZHMpDQogIH0NCiAgDQogIHJldHVybih0aW1lX3N0cikNCn0NCg0KI2lkZW50aWZ5IHBlYWtfcG0gY2F0ZWdvcnkgDQpkYXQgPC0gZGF0ICU+JQ0KICBtdXRhdGUoZGVwYXJ0dXJlX3RpbWUgPSBzYXBwbHkoZGVwYXJ0dXJlX3RpbWUsIG5vcm1hbGl6ZV90aW1lKSwgIyBkZXBhcnR1cmVfdGltZSdzIG5vcm1hbGl6ZWQNCiAgICAgICAgIGRlcGFydHVyZV90aW1lID0gbHVicmlkYXRlOjpobXMoZGVwYXJ0dXJlX3RpbWUpLA0KICAgICAgICAgaG91ciA9IGhvdXIoZGVwYXJ0dXJlX3RpbWUpLCAgIyBFeHRyYWN0IGhvdXIgY29tcG9uZW50IGZvciBlYXNpZXIgY29uZGl0aW9uIGNoZWNraW5nDQogICAgICAgICBwZWFrX2NhdGVnb3J5ID0gaWZfZWxzZShob3VyID49IDQgJiBob3VyIDwgNiwgIkVhcmx5IE1vcm5pbmciLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZfZWxzZShob3VyID49IDYgJiBob3VyIDwgOSwgIkFNIFBlYWsiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZl9lbHNlKGhvdXIgPj0gOSAmIGhvdXIgPCAxNSwgIk1pZCBEYXkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmX2Vsc2UoaG91ciA+PSAxNSAmIGhvdXIgPCAxOCwgIlBNIFBlYWsiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZfZWxzZShob3VyID49IDE4ICYgaG91ciA8IDIyLCAiRXZlbmluZyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoaG91ciA+PSAyMiAmIGhvdXIgPCAyNCwgIkxhdGUgTmlnaHQiLCAiT3dsIikpKSkpKSkgDQoNCiMgIyBDaGVjayBpZiB0aGVyZSBhcmUgYW55IE5BIHZhbHVlcyBpbiB0aGUgJ2RlcGFydHVyZV90aW1lJyBjb2x1bW4NCiMgc3VtbWFyaXNlKGRhdCwNCiMgICAgIG5hX2NvdW50ID0gc3VtKGlzLm5hKGRlcGFydHVyZV90aW1lKSksICNDb3VudCBOQSB2YWx1ZXMNCiMgICAgIHRvdGFsX3Jvd3MgPSBuKCksICAjIFRvdGFsIG51bWJlciBvZiByb3dzIGZvciBjb250ZXh0DQojICAgICBwZXJjZW50YWdlX25hID0gbmFfY291bnQgLyB0b3RhbF9yb3dzICogMTAwICAjIFBlcmNlbnRhZ2Ugb2YgTkEgdmFsdWVzDQojICAgKQ0KDQpkdXJhdGlvbnMgPC0gYygiT3dsIiA9IDQsICJFYXJseSBNb3JuaW5nIiA9IDIsICJBTSBQZWFrIiA9IDMsICJNaWQgRGF5IiA9IDYsICJQTSBQZWFrIiA9IDMsICJFdmVuaW5nIiA9IDQsICJMYXRlIE5pZ2h0IiA9IDIpDQoNCmRhdF9zZXJ2aWNlX3BlcmlvZCA8LSBkYXQgJT4lDQogIGZpbHRlcihtb2RlICVpbiUgYygiQnVzIiwgIlRyb2xsZXlidXMiLCAiVHJvbGxleSIpKSAlPiUNCiAgZ3JvdXBfYnkocGVha19jYXRlZ29yeSklPiUNCiAgc3VtbWFyaXNlKHBlYWtfY291bnQgPSBuKCksIC5ncm91cHMgPSAnZHJvcCcpICU+JQ0KICBtdXRhdGUodG90YWxfY291bnQgPSBzdW0ocGVha19jb3VudCksDQogICAgICAgICBwZWFrX3NoYXJlID0gcm91bmQocGVha19jb3VudC9zdW0ocGVha19jb3VudCkqMTAwLCBkaWdpdHMgPSAyKSkgJT4lDQogIGFycmFuZ2UoZmFjdG9yKHBlYWtfY2F0ZWdvcnksIGxldmVscyA9IGMoIk93bCIsICJFYXJseSBNb3JuaW5nIiwgIkFNIFBlYWsiLCAiTWlkIERheSIsICJQTSBQZWFrIiwgIkV2ZW5pbmciLCAiTGF0ZSBOaWdodCIpKSkgJT4lDQogIHVuZ3JvdXAoKQ0KDQojIENyZWF0ZSBhIGRhdGEgZnJhbWUgZm9yIHNlcnZpY2UgcGVyaW9kcw0Kc2VydmljZV9wZXJpb2RzIDwtIGRhdGEuZnJhbWUoDQogIHBlYWtfY2F0ZWdvcnkgPSBjKCJPd2wiLCAiRWFybHkgTW9ybmluZyIsICJBTSBQZWFrIiwgIk1pZCBEYXkiLCAiUE0gUGVhayIsICJFdmVuaW5nIiwgIkxhdGUgTmlnaHQiKSwNCiAgc3RhcnRfaG91ciA9IGMoMCwgNCwgNiwgOSwgMTUsIDE4LCAyMiksDQogIGVuZF9ob3VyID0gYyg0LCA2LCA5LCAxNSwgMTgsIDIyLCAyNCkNCikNCg0KIyBDUkVBVEUgQkFSIENIQVJUIE9GIFRSSVBTIEJZIFNFUlZJQ0UgVFlQRVMNCg0KIyBFbnN1cmUgJ2NhbGVuZGFyJyBoYXMgJ2RheV90eXBlJyBkZWZpbmVkDQpjYWxlbmRhciA8LSBjYWxlbmRhciAlPiUNCiAgbXV0YXRlKA0KICAgIGRheV90eXBlID0gY2FzZV93aGVuKA0KICAgICAgbW9uZGF5ID09IDEgJiB0dWVzZGF5ID09IDEgJiB3ZWRuZXNkYXkgPT0gMSAmIHRodXJzZGF5ID09IDEgJiBmcmlkYXkgPT0gMSAmIHNhdHVyZGF5ID09IDAgJiBzdW5kYXkgPT0gMCB+ICJXZWVrZGF5IiwNCiAgICAgIChzYXR1cmRheSA9PSAxIHwgc3VuZGF5ID09IDEpIH4gIldlZWtlbmQiLA0KICAgICAgVFJVRSB+ICJpcnJlZ3VsYXIiDQogICAgKQ0KICApDQoNCiMgQ2FsZW5kYXJfZGF0ZXMgaW5jbHVkZXMgJ3NlcnZpY2VfaWQnLCAnZGF0ZScsIGFuZCAnZXhjZXB0aW9uX3R5cGUnDQojIEVuc3VyZSAnY2FsZW5kYXJfZGF0ZXMnIGhhcyAnZGF5X3R5cGUnIGRlZmluZWQgYmFzZWQgb24gdGhlIGRhdGUNCmNhbGVuZGFyX2RhdGVzIDwtIGNhbGVuZGFyX2RhdGVzICU+JQ0KICBmaWx0ZXIoZXhjZXB0aW9uX3R5cGUgPT0gMSkgJT4lICN3ZSBvbmx5IG5lZWQgZXhjZXB0aW9uX3R5cGUgPT0gMSwgdGhvc2UgYWRkZWQgdG8gdGhlIHNlcnZpY2UNCiAgbXV0YXRlKA0KICAgIGRhdGUgPSB5bWQoZGF0ZSksICAjIENvbnZlcnQgZGF0ZSBmb3JtYXQgaWYgbmVjZXNzYXJ5DQogICAgZGF5X29mX3dlZWsgPSB3ZGF5KGRhdGUsIGxhYmVsID0gVFJVRSwgd2Vla19zdGFydCA9IDEpLA0KICAgIGRheV90eXBlID0gY2FzZV93aGVuKA0KICAgICAgZGF5X29mX3dlZWsgJWluJSBjKCJNb24iLCAiVHVlIiwgIldlZCIsICJUaHUiLCAiRnJpIikgfiAiV2Vla2RheSIsICAjIE1vbmRheSB0byBGcmlkYXkNCiAgICAgIFRSVUUgfiAiV2Vla2VuZCIgICMgU2F0dXJkYXkgYW5kIFN1bmRheQ0KICAgICkNCiAgKQ0KDQojIEpvaW4gY2FsZW5kYXJfZGF0ZXMgd2l0aCBjYWxlbmRhcg0KY2FsZW5kYXJfam9pbmVkIDwtIGNhbGVuZGFyX2RhdGVzICU+JQ0KICBmdWxsX2pvaW4oY2FsZW5kYXIsIGJ5ID0gInNlcnZpY2VfaWQiLCBzdWZmaXggPSBjKCIuZGF0ZXMiLCAiLmNhbCIpKSAlPiUNCiAgbXV0YXRlKA0KICAgIGZpbmFsX2RheV90eXBlID0gY2FzZV93aGVuKA0KICAgICAgIyBVc2UgY2FsZW5kYXJfZGF0ZXMnIGRheV90eXBlIGlmIGV4Y2VwdGlvbl90eXBlIGluZGljYXRlcyBhZGRlZCBzZXJ2aWNlDQogICAgICBleGNlcHRpb25fdHlwZSA9PSAxICYgZGF5X3R5cGUuZGF0ZXMgPT0gIldlZWtkYXkiIH4gIldlZWtkYXkiLA0KICAgICAgZXhjZXB0aW9uX3R5cGUgPT0gMSAmIGRheV90eXBlLmRhdGVzID09ICJXZWVrZW5kIiB+ICJXZWVrZW5kIiwNCiAgICAgICMgRGVmYXVsdCB0byBjYWxlbmRhcidzIGRheV90eXBlIHdoZXJlIHRoZXJlJ3Mgbm8gc3BlY2lmaWMgZXhjZXB0aW9uIG5vdGVkDQogICAgICBUUlVFIH4gZGF5X3R5cGUuY2FsDQogICAgKQ0KICApICU+JQ0KICBzZWxlY3Qoc2VydmljZV9pZCwgZmluYWxfZGF5X3R5cGUpICU+JQ0KICBncm91cF9ieShzZXJ2aWNlX2lkLCBmaW5hbF9kYXlfdHlwZSkgJT4lDQogIHN1bW1hcmlzZShjb3VudCA9IG4oKSwgLmdyb3VwcyA9ICdkcm9wJykgJT4lDQogIGFycmFuZ2Uoc2VydmljZV9pZCwgZGVzYyhjb3VudCkpICU+JQ0KICBkaXN0aW5jdChzZXJ2aWNlX2lkLCAua2VlcF9hbGwgPSBUUlVFKSAlPiUgIyBNYWtlIHN1cmUgbm8gZG91YmxlIGRheSB0eXBlcyBmb3Igc2VydmljZSBJRA0KICBzZWxlY3QoLWNvdW50KQ0KICANCmRhdCA8LSBkYXQgJT4lDQogIGxlZnRfam9pbihjYWxlbmRhcl9qb2luZWQsIGJ5ID0gInNlcnZpY2VfaWQiKQ0KDQpkYXRfc2VydmljZV90eXBlIDwtIGRhdCAlPiUNCiAgZmlsdGVyKG1vZGUgJWluJSBjKCJCdXMiLCAiVHJvbGxleWJ1cyIsICJUcm9sbGV5IikpICU+JQ0KICBncm91cF9ieShmaW5hbF9kYXlfdHlwZSklPiUNCiAgc3VtbWFyaXNlKGRheV90eXBlX2NvdW50ID0gbigpLCAuZ3JvdXBzID0gJ2Ryb3AnKSAlPiUNCiAgbXV0YXRlKHRvdGFsX2NvdW50ID0gc3VtKGRheV90eXBlX2NvdW50KSwNCiAgICAgICAgIHBlYWtfc2hhcmUgPSByb3VuZChkYXlfdHlwZV9jb3VudC9zdW0oZGF5X3R5cGVfY291bnQpKjEwMCwgZGlnaXRzID0gMikpICU+JQ0KICBhcnJhbmdlKGZhY3RvcihmaW5hbF9kYXlfdHlwZSwgbGV2ZWxzID0gYygiV2Vla2RheSIsICJXZWVrZW5kIikpKSAlPiUNCiAgdW5ncm91cCgpDQoNCiMgU3VtbWFyaXplIGRhdGEgdG8gZ2V0IHRoZSBudW1iZXIgb2YgdHJpcHMgcGVyIGhvdXINCmhvdXJseV90cmlwcyA8LSBkYXQgJT4lDQogIGZpbHRlcihtb2RlICVpbiUgYygiQnVzIiwgIlRyb2xsZXlidXMiLCAiVHJvbGxleSIpKSAlPiUNCiAgZ3JvdXBfYnkoaG91ciwgZmluYWxfZGF5X3R5cGUpICU+JQ0KICBzdW1tYXJpc2UobnVtYmVyX29mX3RyaXBzID0gbigpKQ0KDQojIERlZmluZSBhIGN1c3RvbSBwYWxldHRlIGZvciB0aGUgc2VydmljZSBwZXJpb2RzDQpzZXJ2aWNlX3BlcmlvZF9jb2xvcnMgPC0gYygNCiAgIk93bCIgPSAiI0U1RTVFNSIsDQogICJFYXJseSBNb3JuaW5nIiA9ICIjQzVFMEI0IiwNCiAgIkFNIFBlYWsiID0gIiNBOUQwOEUiLA0KICAiTWlkIERheSIgPSAiI0ZGRDk2NiIsDQogICJQTSBQZWFrIiA9ICIjRjRCMDg0IiwNCiAgIkV2ZW5pbmciID0gIiNENUE2QkQiLA0KICAiTGF0ZSBOaWdodCIgPSAiI0E0QzJGNCINCikNCg0KIyBQbG90IHRoZSBjb21iaW5lZCBncmFwaA0KZ2dwbG90KCkgKw0KICAjIEFkZCBzZXJ2aWNlIHBlcmlvZCBiYWNrZ3JvdW5kIHJlY3RhbmdsZXMNCiAgZ2VvbV9yZWN0KGRhdGEgPSBzZXJ2aWNlX3BlcmlvZHMsIGFlcyh4bWluID0gc3RhcnRfaG91ciwgeG1heCA9IGVuZF9ob3VyLCB5bWluID0gMCwgeW1heCA9IG1heChob3VybHlfdHJpcHMkbnVtYmVyX29mX3RyaXBzKSwgZmlsbCA9IHBlYWtfY2F0ZWdvcnkpLCBhbHBoYSA9IDAuMykgKw0KICAjIEFkZCB0aGUgbGluZSBwbG90IGZvciB3ZWVrZGF5IHRyaXBzDQogIGdlb21fbGluZShkYXRhID0gaG91cmx5X3RyaXBzICU+JSBmaWx0ZXIoZmluYWxfZGF5X3R5cGUgPT0gIldlZWtkYXkiKSwgYWVzKHggPSBob3VyLCB5ID0gbnVtYmVyX29mX3RyaXBzKSwgY29sb3IgPSAiI0YyQUUyRSIsIHNpemUgPSAxKSArDQogIGdlb21fcG9pbnQoZGF0YSA9IGhvdXJseV90cmlwcyAlPiUgZmlsdGVyKGZpbmFsX2RheV90eXBlID09ICJXZWVrZGF5IiksIGFlcyh4ID0gaG91ciwgeSA9IG51bWJlcl9vZl90cmlwcyksIGNvbG9yID0gIiMyNkE2OTkiLCBzaXplID0gMikgKw0KICAjIEFkZCB0aGUgbGluZSBwbG90IGZvciB3ZWVrZW5kIHRyaXBzDQogIGdlb21fbGluZShkYXRhID0gaG91cmx5X3RyaXBzICU+JSBmaWx0ZXIoZmluYWxfZGF5X3R5cGUgPT0gIldlZWtlbmQiKSwgYWVzKHggPSBob3VyLCB5ID0gbnVtYmVyX29mX3RyaXBzKSwgY29sb3IgPSAiI0E2MTIzQSIsIHNpemUgPSAxKSArDQogIGdlb21fcG9pbnQoZGF0YSA9IGhvdXJseV90cmlwcyAlPiUgZmlsdGVyKGZpbmFsX2RheV90eXBlID09ICJXZWVrZW5kIiksIGFlcyh4ID0gaG91ciwgeSA9IG51bWJlcl9vZl90cmlwcyksIGNvbG9yID0gIiMyNkE2OTkiLCBzaXplID0gMikgKw0KICAjIERlZmluZSBzY2FsZXMgYW5kIGxhYmVscw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gMDoyMykgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gc2VydmljZV9wZXJpb2RfY29sb3JzKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJXZWVrZGF5IiA9ICIjRjJBRTJFIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJXZWVrZW5kIiA9ICIjQTYxMjNBIikpICsNCiAgbGFicyh0aXRsZSA9ICJOdW1iZXIgb2YgVHJpcHMgYnkgSG91ciB3aXRoIFNlcnZpY2UgUGVyaW9kcyIsDQogICAgICAgeCA9ICJIb3VyIG9mIERheSIsDQogICAgICAgeSA9ICJOdW1iZXIgb2YgVHJpcHMiLA0KICAgICAgIGZpbGwgPSAiU2VydmljZSBQZXJpb2QiLA0KICAgICAgIGNvbG9yID0gIkRheSBUeXBlIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpDQogIA0KYGBgDQoNCldoaWxlIGJ1cyBzZXJ2aWNlcyBhcmUgaW50ZWdyYWwgdG8gUGhpbGFkZWxwaGlhJ3MgdHJhbnNpdCBzeXN0ZW0sIG5vdCBhbGwgYXJlYXMgYmVuZWZpdCBlcXVhbGx5LiBEYXRhIHNwZWNpZmljYWxseSBwZXJ0YWluaW5nIHRvIHN1YnVyYmFuIHJlZ2lvbnMgZHVyaW5nIHdlZWtkYXkgQU0gYW5kIFBNIHBlYWsgaG91cnMgcmV2ZWFscyBhIHN0YXJrIGRpc3Bhcml0eTogbW9zdCBzdWJ1cmJhbiBidXMgc3RhdGlvbnMgZmFjZSB3YWl0aW5nIHRpbWVzIHJhbmdpbmcgZnJvbSAzMCB0byA2MCBtaW51dGVzIGJldHdlZW4gYnVzZXMuIFRoaXMgc2lnbmlmaWNhbnQgd2FpdCB0aW1lIG5vdCBvbmx5IHVuZGVyc2NvcmVzIHRoZSBjaGFsbGVuZ2VzIGZhY2VkIGJ5IGNvbW11dGVycyBsaWtlIE1pZGdlIGJ1dCBhbHNvIGhpZ2hsaWdodHMgYSBicm9hZGVyIGlzc3VlIGFmZmVjdGluZyBtYW55IHN1YnVyYmFuIHJlc2lkZW50cyBhY3Jvc3MgU291dGhlYXN0ZXJuIFBlbm5zeWx2YW5pYSwgd2hvIHJlbHkgaGVhdmlseSBvbiBidXMgdHJhbnNpdC4gIA0KDQpgYGB7ciBzZXQgdXAgc3RvcHMgY2F0ZWdvcnksIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUUsIHJlc3VsdHMgPSAnaGlkZSd9DQojIFNFVCBVUCBTVE9QUyBDQVRFR09SWS4gVGhpcyBzdGVwIGlzIGNydWNpYWwgYXMgd2UnbGwgdXNlIHN0b3BzIGluZm9ybWF0aW9uIGFuZCBqb2luIHRoZW0gZm9yIGVhY2gga2luZCBvZiBtYXBzIA0KDQojIENvbnZlcnQgc3RvcHMgZGF0YSB0byBzZiBvYmplY3QNCnN0b3BzIDwtIHN0b3BzICU+JQ0KICBzdF9hc19zZihjb29yZHMgPSBjKCJzdG9wX2xvbiIsICJzdG9wX2xhdCIpLCBjcnMgPSA0MzI2LCByZW1vdmUgPSBGQUxTRSkNCg0KIyBMb2FkIFBBIGFuZCBTRVBUQSByZWdpb24gbWFwIGZvciBiYXNlIG1hcA0KUEEgPC0gc3RfcmVhZCgiZGF0YS9QYUNvdW50eTIwMjRfMDUuZ2VvanNvbiIpDQpTRVBUQV9yZWdpb25fbWFwIDwtIFBBW1BBJENPVU5UWV9OQU0gJWluJSBjKCJNT05UR09NRVJZIiwgIkJVQ0tTIiwgIlBISUxBREVMUEhJQSIsICJERUxBV0FSRSIsICJDSEVTVEVSIiksIF0gJT4lDQogIHNlbGVjdChQQV9DVFlfQ09ELCBDT1VOVFlfTkFNLCBGSVBTX0NPVU5UKQ0KDQojIExvYWQgQ2VudGVyIENpdHkgYm91bmRhcnkNCmNlbnRlcl9jaXR5IDwtIHN0X3JlYWQoImRhdGEvMjAyNDA3MDhfQ2VudGVyQ2l0eS9DZW50ZXJDaXR5LnNocCIpDQoNCiMgRW5zdXJlIGFsbCBkYXRhc2V0cyBhcmUgaW4gdGhlIHNhbWUgQ1JTDQpTRVBUQV9yZWdpb25fbWFwIDwtIHN0X3RyYW5zZm9ybShTRVBUQV9yZWdpb25fbWFwLCBjcnMgPSBzdF9jcnMoc3RvcHMpKQ0KY2VudGVyX2NpdHkgPC0gc3RfdHJhbnNmb3JtKGNlbnRlcl9jaXR5LCBjcnMgPSBzdF9jcnMoc3RvcHMpKQ0KDQojIFBlcmZvcm0gdGhlIHNwYXRpYWwgaW50ZXJzZWN0aW9uIGZvciBjb3VudHkNCnN0b3BzIDwtIHN0b3BzICU+JQ0KICBzdF9qb2luKFNFUFRBX3JlZ2lvbl9tYXAsIGxlZnQgPSBUUlVFLCBqb2luID0gc3RfaW50ZXJzZWN0cykgJT4lDQogIHJlbmFtZShjb3VudHkgPSBDT1VOVFlfTkFNKSAlPiUNCiAgbXV0YXRlKGNlbnRlcl9jaXR5ID0gc3Rfd2l0aGluKHN0b3BzLCBjZW50ZXJfY2l0eSwgc3BhcnNlID0gRkFMU0UpKSU+JQ0KICBtdXRhdGUoY2VudGVyX2NpdHkgPSByb3dTdW1zKGNlbnRlcl9jaXR5KSA+IDApICAjIENvbnZlcnQgbG9naWNhbCBtYXRyaXggdG8gdmVjdG9yDQoNCiMgQ2F0ZWdvcml6ZSB0aGUgc3RvcHMNCnN0b3BzIDwtIHN0b3BzICU+JQ0KICBtdXRhdGUoY2F0ZWdvcnkgPSBjYXNlX3doZW4oDQogICAgY291bnR5ID09ICJQSElMQURFTFBISUEiICYgY2VudGVyX2NpdHkgPT0gVFJVRSB+ICJQSElMQURFTFBISUEtQ2VudGVyX0NpdHkiLA0KICAgIGNvdW50eSA9PSAiUEhJTEFERUxQSElBIiAmIGNlbnRlcl9jaXR5ID09IEZBTFNFIH4gIlBISUxBREVMUEhJQS1Ob3RfQ2VudGVyX0NpdHkiLA0KICAgIFRSVUUgfiBjb3VudHkNCiAgKSkgJT4lDQogIHNlbGVjdCgtImNlbnRlcl9jaXR5IikNCiAgDQpgYGANCg0KYGBge3IgaG91cmx5IHN0b3AgZGF0YSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSwgcmVzdWx0cyA9ICdoaWRlJ30NCiMgU0VUIFVQIEhPVVJMWSBBVkVSQUdFIFRSSVAgREFUQSBGT1IgRUFDSCBCVVMgU1RPUA0KDQpkYXRfdHJpcF9jb3VudF9ociA8LSBkYXQgJT4lDQogIGZpbHRlcihtb2RlICVpbiUgYygiQnVzIiwgIlRyb2xsZXlidXMiLCAiVHJvbGxleSIpKSAlPiUNCiAgZ3JvdXBfYnkoc3RvcF9pZCwgbW9kZSwgZmluYWxfZGF5X3R5cGUpICU+JQ0KICBzdW1tYXJpc2UodHJpcF9jb3VudCA9IG4oKSwgDQogICAgICAgICAgICBBbGxfUm91dGVzID0gcGFzdGUodW5pcXVlKHJvdXRlX2lkKSwgY29sbGFwc2UgPSAiLCAiKSwNCiAgICAgICAgICAgIC5ncm91cHMgPSAnZHJvcCcpICU+JQ0KICBtdXRhdGUoYXZnX3RyaXBfaHIgPSByb3VuZCh0cmlwX2NvdW50LzI0KSkgJT4lDQogIG11dGF0ZShsb2dfYXZnX3RyaXBfaHIgPSByb3VuZChsb2cxcChhdmdfdHJpcF9ociksIGRpZ2l0cyA9IDApKSAlPiUNCiAgbXV0YXRlKG1heF9mcmVxID0gaWZlbHNlKGF2Z190cmlwX2hyID49IDEyLCAiNSBNQVgiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlIChhdmdfdHJpcF9ociA+PSA2LCAiMTAgTUFYIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZSAoYXZnX3RyaXBfaHIgPj0gNCwgIjE1IE1BWCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGF2Z190cmlwX2hyID49IDIsICIzMCBNQVgiLCAiNjAgTUFYIikpKSkpDQoNCmRhdF90cmlwX2NvdW50X2hyIDwtIGxlZnRfam9pbihkYXRfdHJpcF9jb3VudF9ociwgc3RvcHMsIGJ5ID0gInN0b3BfaWQiKQ0KDQpkYXRfdHJpcF9jb3VudF9ociA8LSBzdF9hc19zZihkYXRfdHJpcF9jb3VudF9ociwgY29vcmRzID0gYygic3RvcF9sb24iLCAic3RvcF9sYXQiKSwgY3JzID0gNDMyNiwgcmVtb3ZlID0gRkFMU0UpDQoNCiMgc3VtKGRhdF90cmlwX2NvdW50X2hyJHRyaXBfY291bnQpICMxOTgwNTA1IDwtIEFsd2F5cyBtYWtlIHN1cmUgeW91IHJldGFpbiB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9uDQoNCmBgYA0KDQpgYGB7ciBzZXJ2aWNlIHBlcmlvZCBzdG9wIGRhdGEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUUsIHJlc3VsdHMgPSAnaGlkZSd9DQojIFNFVCBVUCBBVkVSQUdFIFRSSVAgREFUQSBQRVIgU0VSVklDRSBQRVJJT0QgRk9SIEVBQ0ggQlVTIFNUT1ANCg0KZGF0X3RyaXBfY291bnRfcHJkIDwtIGRhdCAlPiUNCiAgZmlsdGVyKG1vZGUgJWluJSBjKCJCdXMiLCAiVHJvbGxleWJ1cyIsICJUcm9sbGV5IikpICU+JQ0KICBncm91cF9ieShzdG9wX2lkLCBwZWFrX2NhdGVnb3J5LCBtb2RlLCBmaW5hbF9kYXlfdHlwZSkgJT4lDQogIHN1bW1hcmlzZSh0cmlwX2NvdW50ID0gbigpLCANCiAgICAgICAgICAgIEFsbF9Sb3V0ZXMgPSBwYXN0ZSh1bmlxdWUocm91dGVfaWQpLCBjb2xsYXBzZSA9ICIsICIpLA0KICAgICAgICAgICAgLmdyb3VwcyA9ICdkcm9wJykgJT4lDQogIG11dGF0ZSgNCiAgICBob3VycyA9IGR1cmF0aW9uc1twZWFrX2NhdGVnb3J5XSwNCiAgICBhdmdfdHJpcF9ociA9IHJvdW5kKHRyaXBfY291bnQgLyBob3VycykNCiAgKSAlPiUNCiAgc2VsZWN0KC1ob3VycykgJT4lDQogIG11dGF0ZShsb2dfYXZnX3RyaXBfaHIgPSByb3VuZChsb2cxcChhdmdfdHJpcF9ociksIGRpZ2l0cyA9IDApKSAlPiUNCiAgbXV0YXRlKG1heF9mcmVxID0gaWZlbHNlKGF2Z190cmlwX2hyID49IDEyLCAiNSBNQVgiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlIChhdmdfdHJpcF9ociA+PSA2LCAiMTAgTUFYIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZSAoYXZnX3RyaXBfaHIgPj0gNCwgIjE1IE1BWCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGF2Z190cmlwX2hyID49IDIsICIzMCBNQVgiLCAiNjAgTUFYIikpKSkpDQoNCmRhdF90cmlwX2NvdW50X3ByZCA8LSBsZWZ0X2pvaW4oZGF0X3RyaXBfY291bnRfcHJkLCBzdG9wcywgYnkgPSAic3RvcF9pZCIpDQoNCmRhdF90cmlwX2NvdW50X3ByZCA8LSBzdF9hc19zZihkYXRfdHJpcF9jb3VudF9wcmQsIGNvb3JkcyA9IGMoInN0b3BfbG9uIiwgInN0b3BfbGF0IiksIGNycyA9IDQzMjYsIHJlbW92ZSA9IEZBTFNFKQ0KDQojIHN1bShkYXRfdHJpcF9jb3VudF9wcmQkdHJpcF9jb3VudCkgICMxOTgwNTA1IDwtIEFsd2F5cyBtYWtlIHN1cmUgeW91IHJldGFpbiB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9uDQpgYGANCg0KYGBge3IgYXZlcmFnZSBidXMgaG91cmx5IG1heGZyZXEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPUZBTFNFfQ0KU0VQVEFfcmVnaW9uX21hcCA8LSBzdF90cmFuc2Zvcm0oU0VQVEFfcmVnaW9uX21hcCwgc3RfY3JzKGRhdF90cmlwX2NvdW50X2hyKSkNCg0KIyAjIERlZmluZSBjb2xvciBwYWxldHRlDQojIHBhbCA8LSBjb2xvck51bWVyaWMocGFsZXR0ZSA9IHZpcmlkaXNMaXRlOjp2aXJpZGlzKDI1Niwgb3B0aW9uID0gIkMiKSwgZG9tYWluID0gZGF0X3RyaXBfY291bnRfaHIkYXZnX3RyaXBfaHIpDQoNCiMgRGVmaW5lIHRoZSBjb2xvciBjb2RlcyBmb3IgYnVzIHJldm9sdXRpb24gYW5kIHJldmVyc2UgdGhlbQ0KYnVzX3Jldm9sdXRpb25fY29sb3JzIDwtIHJldihjKCIjQTYxMjNBIiwgIiM2NTVCQTYiLCAiIzI2QTY5OSIsICIjRjJBRTJFIiwgIiNGMkU1RDUiKSkNCg0KIyBFbnN1cmUgbWF4X2ZyZXEgaXMgYSBmYWN0b3Igd2l0aCBsZXZlbHMgbWF0Y2hpbmcgdGhlIGNvbG9ycw0KZGF0X3RyaXBfY291bnRfaHIkbWF4X2ZyZXEgPC0gZmFjdG9yKGRhdF90cmlwX2NvdW50X2hyJG1heF9mcmVxLCBsZXZlbHMgPSBjKCI1IE1BWCIsICIxMCBNQVgiLCAiMTUgTUFYIiwgIjMwIE1BWCIsICI2MCBNQVgiKSkNCg0KIyBEZWZpbmUgdGhlIGNvbG9yIHBhbGV0dGUgZm9yIG1heF9mcmVxDQptYXhfZnJlcV9jb2xvcnMgPC0gYygiNSBNQVgiID0gIiM0QjAwODIiLCAiMTAgTUFYIiA9ICIjRkY0NTAwIiwgIjE1IE1BWCIgPSAiI0E2MTIzQSIsICIzMCBNQVgiID0gIiMyNkE2OTkiLCAiNjAgTUFYIiA9ICIjRjJBRTJFIikNCnBhbF9tYXhfZnJlcSA8LSBjb2xvckZhY3RvcihwYWxldHRlID0gbWF4X2ZyZXFfY29sb3JzLCBkb21haW4gPSBkYXRfdHJpcF9jb3VudF9ociRtYXhfZnJlcSkNCg0KIyAjIEdlbmVyYXRlIHRoZSBsaXN0IGZvciBvdmVybGF5R3JvdXBzIHdpdGggdGhlIGRlc2lyZWQgb3JkZXINCiMgb3ZlcmxheV9ncm91cHMgPC0gZXhwYW5kLmdyaWQoZmluYWxfZGF5X3R5cGUgPSBjKCJXZWVrZGF5IiwgIldlZWtlbmQiKSwgcGVha19jYXRlZ29yeSA9IHBlYWtfb3JkZXIpICU+JQ0KIyAgIG11dGF0ZShncm91cF9uYW1lID0gcGFzdGUoZmluYWxfZGF5X3R5cGUsIHBlYWtfY2F0ZWdvcnksIHNlcCA9ICJfIikpICU+JQ0KIyAgIHB1bGwoZ3JvdXBfbmFtZSkNCg0KIyBDcmVhdGUgdGhlIGxlYWZsZXQgbWFwDQptX2F2Z190cmlwX2hyIDwtIGxlYWZsZXQoKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lDQogIGFkZFBvbHlnb25zKGRhdGEgPSBTRVBUQV9yZWdpb25fbWFwLCBjb2xvciA9ICJibGFjayIsIHdlaWdodCA9IDEsIGZpbGwgPSBGQUxTRSkgJT4lDQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IGRhdF90cmlwX2NvdW50X2hyICU+JSBmaWx0ZXIoZmluYWxfZGF5X3R5cGUgPT0gIldlZWtkYXkiKSwNCiAgICAgICAgICAgICAgICAgICBncm91cCA9ICJXZWVrZGF5IiwNCiAgICAgICAgICAgICAgICAgICBsbmcgPSB+c3RvcF9sb24sIGxhdCA9IH5zdG9wX2xhdCwNCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IH5wYWxfbWF4X2ZyZXEobWF4X2ZyZXEpLA0KICAgICAgICAgICAgICAgICAgIHJhZGl1cyA9IDEsDQogICAgICAgICAgICAgICAgICAgb3BhY2l0eSA9IDAuOCwNCiAgICAgICAgICAgICAgICAgICBwb3B1cCA9IH5wYXN0ZTAoIjxiPlN0b3AgSUQ6PC9iPiAiLCBzdG9wX2lkLCAiPGJyPiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8Yj5TdG9wIE5hbWU6PC9iPiAiLCBzdG9wX25hbWUsICI8YnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjxiPkF2ZyBCdXNlcy9Icjo8L2I+ICIsIGF2Z190cmlwX2hyLCAiPGJyPiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8Yj5NYXggRnJlcXVlbmN5OjwvYj4gIiwgbWF4X2ZyZXEsICI8YnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjxiPkFsbCBSb3V0ZXM6PC9iPiAiLCBBbGxfUm91dGVzLCAiPGJyPiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8Yj5EYXkgVHlwZTo8L2I+ICIsIGZpbmFsX2RheV90eXBlKSkgJT4lDQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IGRhdF90cmlwX2NvdW50X2hyICU+JSBmaWx0ZXIoZmluYWxfZGF5X3R5cGUgPT0gIldlZWtlbmQiKSwNCiAgICAgICAgICAgICAgICAgICBncm91cCA9ICJXZWVrZW5kIiwNCiAgICAgICAgICAgICAgICAgICBsbmcgPSB+c3RvcF9sb24sIGxhdCA9IH5zdG9wX2xhdCwNCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IH5wYWxfbWF4X2ZyZXEobWF4X2ZyZXEpLA0KICAgICAgICAgICAgICAgICAgIHJhZGl1cyA9IDEsDQogICAgICAgICAgICAgICAgICAgb3BhY2l0eSA9IDAuOCwNCiAgICAgICAgICAgICAgICAgICBwb3B1cCA9IH5wYXN0ZTAoIjxiPlN0b3AgSUQ6PC9iPiAiLCBzdG9wX2lkLCAiPGJyPiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8Yj5TdG9wIE5hbWU6PC9iPiAiLCBzdG9wX25hbWUsICI8YnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjxiPkF2ZyBCdXNlcy9Icjo8L2I+ICIsIGF2Z190cmlwX2hyLCAiPGJyPiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8Yj5NYXggRnJlcXVlbmN5OjwvYj4gIiwgbWF4X2ZyZXEsICI8YnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjxiPkFsbCBSb3V0ZXM6PC9iPiAiLCBBbGxfUm91dGVzLCAiPGJyPiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8Yj5EYXkgVHlwZTo8L2I+ICIsIGZpbmFsX2RheV90eXBlKSkgJT4lDQogIGFkZExheWVyc0NvbnRyb2woDQogICAgb3ZlcmxheUdyb3VwcyA9IGMoIldlZWtkYXkiLCAiV2Vla2VuZCIpLA0KICAgIG9wdGlvbnMgPSBsYXllcnNDb250cm9sT3B0aW9ucyhjb2xsYXBzZWQgPSBGQUxTRSkNCiAgKSAlPiUNCiAgaGlkZUdyb3VwKGMoIldlZWtlbmQiKSkgJT4lDQogIGFkZExlZ2VuZChwYWwgPSBwYWxfbWF4X2ZyZXEsIHZhbHVlcyA9IGRhdF90cmlwX2NvdW50X2hyJG1heF9mcmVxLCB0aXRsZSA9ICJNYXggRnJlcXVlbmN5IiwNCiAgICAgICAgICAgIHBvc2l0aW9uID0gImJvdHRvbXJpZ2h0IikNCg0KbV9hdmdfdHJpcF9ocg0KYGBgDQoNCkZ1cnRoZXIgYW5hbHlzaXMgb2YgYnVzIHN0b3AgZGF0YSBieSBjb3VudHnigJRleGNsdWRpbmcgUGhpbGFkZWxwaGlhJ3MgQ2VudGVyIENpdHnigJRyZXZlYWxzIGEgbWlzbGVhZGluZyBlcXVhbGl0eSBpbiBzZXJ2aWNlIGRpc3RyaWJ1dGlvbiBhY3Jvc3MgdGhlIGNvdW50aWVzLiBXaGlsZSB0aGUgdG9wIGJ1cyBzdG9wcyBpbiBlYWNoIGNvdW50eSBib2FzdCBhIDUtbWludXRlIG1heGltdW0gaGVhZHdheSwgcmVmbGVjdGluZyBzZWVtaW5nbHkgZXF1YWwgc2VydmljZSBsZXZlbHMsIHRoZSBhY3R1YWwgbnVtYmVyIG9mIGJ1c2VzIHRlbGxzIGEgZGlmZmVyZW50IHN0b3J5LiBGb3IgaW5zdGFuY2UsIGV2ZW4gYXQgdGhlaXIgYnVzaWVzdCwgYnVzIHN0b3BzIGluIEJ1Y2tzIENvdW50eSBzZWUgYW4gYXZlcmFnZSBvZiBvbmx5IDE1IGJ1c2VzIHBlciBob3VyIGF0IHRoZSA1IE1BWCBjYXRlZ29yeSwgc3RhcmtseSBsZXNzIHRoYW4gdGhlIDMwIGJ1c2VzIHNlZW4gaW4gc2ltaWxhciBjYXRlZ29yaWVzIGluIFBoaWxhZGVscGhpYeKAmXMgb3V0bHlpbmcgYXJlYXMuIFRoaXMgZGlzY3JlcGFuY3kgaGlnaGxpZ2h0cyB0aGUgY29uY2VudHJhdGlvbiBvZiBiZXR0ZXIgc2VydmljZXMgc3RpbGwgd2l0aGluIGNsb3NlciBwcm94aW1pdHkgdG8gdXJiYW4gY2VudGVycywgZGVzcGl0ZSBhdHRlbXB0cyBhdCBlcXVpdGFibGUgZGlzdHJpYnV0aW9uLg0KDQpgYGB7ciB0b3AgMTAgc3RvcHMgdGFibGUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUV9DQojIEZpbHRlciBhbmQgc2VsZWN0IHRoZSB0b3AgMTAgc3RvcHMgZm9yICdtb2RlJyA9PSAnYnVzJyBvbiB0aGUgd2Vla2RheQ0KdG9wX3N0b3BzX2hyX3dlZWtkYXkgPC0gZGF0X3RyaXBfY291bnRfaHIgJT4lDQogIGZpbHRlcihtb2RlICVpbiUgYygiQnVzIiwiVHJvbGxleWJ1cyIpLCBmaW5hbF9kYXlfdHlwZSA9PSAiV2Vla2RheSIpICU+JQ0KICBzZWxlY3Qoc3RvcF9pZCwgc3RvcF9uYW1lLCBhdmdfdHJpcF9ociwgQWxsX1JvdXRlcykgJT4lDQogIGFycmFuZ2UoZGVzYyhhdmdfdHJpcF9ocikpICU+JQ0KICBzbGljZV9oZWFkKG4gPSAxMCkNCg0KIyBDcmVhdGUgYSB0YWJsZSB3aXRoIGthYmxlIGFuZCBlbmhhbmNlIGl0IHdpdGgga2FibGVFeHRyYQ0Ka2FibGUodG9wX3N0b3BzX2hyX3dlZWtkYXksICJodG1sIiwgYm9va3RhYnMgPSBUUlVFLCANCiAgICAgIGNhcHRpb24gPSAiVG9wIDEwIEJ1cyBTdG9wcyBieSBBdmVyYWdlIFRyaXBzIFBlciBIb3VyIG9uIHRoZSBXZWVrZGF5IiwNCiAgICAgIGFsaWduID0gYygnbCcsIHJlcCgnbCcsIG5jb2wodG9wX3N0b3BzX2hyX3dlZWtkYXkpIC0gMSkpKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiksIGZ1bGxfd2lkdGggPSBGQUxTRSkgJT4lDQogIGNvbHVtbl9zcGVjKDEsIGJvbGQgPSBUUlVFLCBjb2xvciA9ICIjNjU1QkE2Iiwgd2lkdGggPSAiNWVtIikgJT4lDQogIGNvbHVtbl9zcGVjKDMsIGJvbGQgPSBUUlVFLCBjb2xvciA9ICIjQTYxMjNBIiwgZXh0cmFfY3NzID0gInRleHQtYWxpZ246IHJpZ2h0OyIpDQoNCmBgYA0KDQoNCmBgYHtyIHRvcCA1IHN0b3BzIHRhYmxlLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFfQ0KIyBEZWZpbmUgYSBjdXN0b20gcGFsZXR0ZSBmb3IgdGhlIGNhdGVnb3JpZXMNCmNhdGVnb3J5X2NvbG9ycyA8LSBjKA0KICAiQlVDS1MiID0gIiNBOUQwOEUiLA0KICAiQ0hFU1RFUiIgPSAiI0ZGRDk2NiIsDQogICJERUxBV0FSRSIgPSAiI0Y0QjA4NCIsDQogICJNT05UR09NRVJZIiA9ICIjRDVBNkJEIiwNCiAgIlBISUxBREVMUEhJQS1Ob3RfQ2VudGVyX0NpdHkiID0gIiNBNEMyRjQiDQopDQoNCiMgRXhhbXBsZSB3aXRoIFdlZWtkYXkgZGF0YQ0KZGF0X2ZpbHRlcmVkX3dlZWtkYXkgPC0gZGF0X3RyaXBfY291bnRfaHIgJT4lDQogIGZpbHRlcihtb2RlICVpbiUgYygiQnVzIiwiVHJvbGxleWJ1cyIpLCBmaW5hbF9kYXlfdHlwZSA9PSAiV2Vla2RheSIgJiBjYXRlZ29yeSAhPSAiUEhJTEFERUxQSElBLUNlbnRlcl9DaXR5IikNCg0KIyBGdW5jdGlvbiB0byBnZXQgdG9wIDUgc3RvcHMgcGVyIGNhdGVnb3J5DQpnZXRfdG9wX3N0b3BzX3Blcl9jYXRlZ29yeSA8LSBmdW5jdGlvbihkYXRhLCBuID0gNSkgew0KICBkYXRhICU+JQ0KICAgIGdyb3VwX2J5KGNhdGVnb3J5KSAlPiUNCiAgICBzZWxlY3Qoc3RvcF9pZCwgc3RvcF9uYW1lLCBhdmdfdHJpcF9ociwgbWF4X2ZyZXEsIEFsbF9Sb3V0ZXMsIGNhdGVnb3J5KSAlPiUNCiAgICBhcnJhbmdlKGRlc2MoYXZnX3RyaXBfaHIpKSAlPiUNCiAgICBzbGljZV9oZWFkKG4gPSBuKSAlPiUNCiAgICB1bmdyb3VwKCkNCn0NCg0KIyBHZXQgdG9wIDUgc3RvcHMgcGVyIGNhdGVnb3J5IGZvciBXZWVrZGF5DQp0b3Bfc3RvcHNfaHJfd2Vla2RheSA8LSBnZXRfdG9wX3N0b3BzX3Blcl9jYXRlZ29yeShkYXRfZmlsdGVyZWRfd2Vla2RheSwgbiA9IDUpDQoNCiMgQ3JlYXRlIHRoZSB0YWJsZSBmb3IgV2Vla2RheSB3aXRoIHJvdyBjb2xvcnMNCmthYmxlKHRvcF9zdG9wc19ocl93ZWVrZGF5LCAiaHRtbCIsIGJvb2t0YWJzID0gVFJVRSwgDQogICAgICBjYXB0aW9uID0gIlRvcCA1IEJ1cyBTdG9wcyBieSBBdmVyYWdlIFRyaXBzIFBlciBIb3VyIG9uIHRoZSBXZWVrZGF5IiwNCiAgICAgIGFsaWduID0gYygnbCcsIHJlcCgnbCcsIG5jb2wodG9wX3N0b3BzX2hyX3dlZWtkYXkpIC0gMSkpKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiksIGZ1bGxfd2lkdGggPSBGQUxTRSkgJT4lDQogIGNvbHVtbl9zcGVjKDEsIGJvbGQgPSBUUlVFLCBjb2xvciA9ICIjNjU1QkE2Iiwgd2lkdGggPSAiNWVtIikgJT4lDQogIGNvbHVtbl9zcGVjKDMsIGJvbGQgPSBUUlVFLCBjb2xvciA9ICIjQTYxMjNBIiwgZXh0cmFfY3NzID0gInRleHQtYWxpZ246IHJpZ2h0OyIpIA0KYGBgDQoNCiMjIyBJbXBhY3QgYW5kIElubm92YXRpb246IEVuaGFuY2luZyBTdWJ1cmJhbiBCdXMgU2VydmljZXMNCg0KV2hpbGUgdGhlIHJhdyBkYXRhIGlsbHVtaW5hdGVzIHRoZSBzZXJ2aWNlIGRpc3Bhcml0aWVzLCB0aGUgdHJ1ZSBkZXB0aCBvZiB0aGlzIGlzc3VlIGlzIGJlc3QgY2FwdHVyZWQgdGhyb3VnaCB0aGUgbGl2ZWQgZXhwZXJpZW5jZXMgb2Ygc3VidXJiYW4gY29tbXV0ZXJzIGxpa2UgTWlkZ2UuIFBlcnNvbmFsIHN0b3JpZXMgcmV2ZWFsIHRoZSBicm9hZGVyIGh1bWFuIGltcGFjdDogbWlzc2VkIG9wcG9ydHVuaXRpZXMsIHByb2xvbmdlZCB3b3JrZGF5cywgYW5kIGRpbWluaXNoZWQgZmFtaWx5IGxpZmUgZHVlIHRvIGluZnJlcXVlbnQgYnVzIHNlcnZpY2VzLiBUaGlzIHF1YWxpdGF0aXZlIGluc2lnaHQgcGFpbnRzIGEgdml2aWQgcGljdHVyZSBvZiB0aGUgZGFpbHkgY2hhbGxlbmdlcyBhbmQgdW5kZXJzY29yZXMgdGhlIHVyZ2VuY3kgZm9yIHRhcmdldGVkIGltcHJvdmVtZW50cy4NCg0KU0VQVEEncyBvbmdvaW5nICJCdXMgUmV2b2x1dGlvbiIgcHJvamVjdCBhaW1zIHRvIG1vZGVybml6ZSBhbmQgZW5oYW5jZSBidXMgc2VydmljZXMsIHByb21pc2luZyB1cGRhdGVkIHJvdXRlcyBhbmQgaW1wcm92ZWQgZnJlcXVlbmNpZXMgdGhhdCBoYXZlbid0IGJlZW4gcmV2aXNlZCBzaW5jZSB0aGUgMTk2MHMuIFdoaWxlIHRoZSBpbml0aWF0aXZlIHNlZWtzIHRvIHJlZHVjZSBoZWFkd2F5cyB0byBhIG1heGltdW0gb2YgMzAgbWludXRlcyBldmVuIGluIHRoZSBtb3N0IHJlbW90ZSBzdWJ1cmJhbiBhcmVhcywgaXQgaGFzIGZhY2VkIGRlbGF5cyBhbmQgY3JpdGljaXNtLCBwYXJ0aWN1bGFybHkgY29uY2VybmluZyBpdHMgcG90ZW50aWFsIHRvIGRpc3Byb3BvcnRpb25hdGVseSBpbXBhY3QgbG93LWluY29tZSBuZWlnaGJvcmhvb2RzLiBUaGlzIHRlbnNpb24gaGlnaGxpZ2h0cyB0aGUgY29tcGxleCBpbnRlcnBsYXkgYmV0d2VlbiBpbXByb3Zpbmcgc2VydmljZSBlZmZpY2llbmN5IGFuZCBhZGRyZXNzaW5nIGNvbW11bml0eSBjb25jZXJucyBvZiBnZW50cmlmaWNhdGlvbi4NCg0KQXMgd2UgY29uY2x1ZGUgb3VyIGV4cGxvcmF0aW9uIG9mIHN1YnVyYmFuIGJ1cyB0cmFuc2l0IGRpc3Bhcml0aWVzIGluIFBoaWxhZGVscGhpYSwgdGhlIGNvbXBlbGxpbmcgbmFycmF0aXZlcyBvZiBkYWlseSBjb21tdXRlcnMsIHVuZGVycGlubmVkIGJ5IHJvYnVzdCBTRVBUQSBkYXRhLCBwYWludCBhIGNsZWFyIHBpY3R1cmUgb2YgdGhlIGNoYWxsZW5nZXMgYW5kIHRoZSB1cmdlbnQgbmVlZCBmb3Igc3lzdGVtaWMgaW1wcm92ZW1lbnRzLiBUaGUgIkJ1cyBSZXZvbHV0aW9uIiBwcm9qZWN0LCB3aGlsZSBhIHByb21pc2luZyBzdGVwIHRvd2FyZCBlbmhhbmNpbmcgYnVzIGZyZXF1ZW5jaWVzIGFuZCB1cGRhdGluZyBkZWNhZGVzLW9sZCByb3V0ZXMsIGFsc28gaGlnaGxpZ2h0cyB0aGUgZGVsaWNhdGUgYmFsYW5jZSByZXF1aXJlZCB0byBpbXByb3ZlIHNlcnZpY2UgZWZmaWNpZW5jeSB3aXRob3V0IGV4YWNlcmJhdGluZyBzb2NpYWwgaW5lcXVpdGllcy4NCg0KRW1icmFjaW5nIGlubm92YXRpdmUgc29sdXRpb25zIGFuZCBmb3N0ZXJpbmcgY29tbXVuaXR5IGVuZ2FnZW1lbnQgaW4gdHJhbnNpdCBwbGFubmluZyBhcmUgY3J1Y2lhbC4gQnkgY29tbWl0dGluZyB0byBib3RoIHRlY2hub2xvZ2ljYWwgdXBncmFkZXMgYW5kIGVxdWl0YWJsZSBzZXJ2aWNlIGRpc3RyaWJ1dGlvbiwgU0VQVEEgY2FuIGVuc3VyZSB0aGF0IGV2ZXJ5IGNvbW11dGVyLCByZWdhcmRsZXNzIG9mIHdoZXJlIHRoZXkgbGl2ZSwgaGFzIHJlbGlhYmxlIGFuZCB0aW1lbHkgYWNjZXNzIHRvIHB1YmxpYyB0cmFuc2l0LiBUaGlzIG5vdCBvbmx5IGltcHJvdmVzIGluZGl2aWR1YWwgZGFpbHkgZXhwZXJpZW5jZXMgYnV0IGFsc28gc3VwcG9ydHMgYnJvYWRlciBnb2FscyBvZiBlY29ub21pYyBtb2JpbGl0eSBhbmQgc3VzdGFpbmFibGUgdXJiYW4gZGV2ZWxvcG1lbnQuDQoNCkFzIHN0YWtlaG9sZGVycywgd2UgYXJlIGNhbGxlZCB1cG9uIHRvIHN1cHBvcnQgdGhlc2UgaW5pdGlhdGl2ZXMsIGFkdm9jYXRlIGZvciB0cmFuc3BhcmVudCBhbmQgaW5jbHVzaXZlIHBsYW5uaW5nIHByb2Nlc3NlcywgYW5kIGVuc3VyZSB0aGF0IHRoZSB2b2ljZXMgb2YgYWxsIGNvbW11dGVycywgZXNwZWNpYWxseSB0aG9zZSBtb3N0IGFmZmVjdGVkIGxpa2UgTWlkZ2UsIGFyZSBoZWFyZCBhbmQgYWRkcmVzc2VkIGluIHRoZSBwdXJzdWl0IG9mIGEgdHJ1bHkgY29ubmVjdGVkIGFuZCBlcXVpdGFibGUgdHJhbnNpdCBzeXN0ZW0u